define(['app', 'clipboard', 'ace-language-tools'], function(app, Clipboard) {
    app.controller('MyBatisMapperController', function($scope, $timeout, xUtil, toastr) {
        var self = this;

        self.activeTab = 0;
        self.daoName = '';
        self.modelName = '';
        self.mapperName = '';
        self.templates = {
            dao: '',
            model: '',
            mapper: ''
        };
        self.error = '';
        self.hasSql = false;
        self.hasCode = false;

        var parameters = {
            table: {
                name: '',
                columns: [{
                    name: '',
                    jdbcType: '',
                    javaName: '',
                    javaType: '',
                    primaryKey: false
                }]
            },
            dao: {
                name: '',
                packageName: '',
                className: ''
            },
            model: {
                name: '',
                packageName: '',
                className: ''
            },
            mapper: {
                name: ''
            }
        };

        var sqlEditor = null,
            daoViewer = null,
            modelViewer = null,
            mapperViewer = null,
            javaOptions = {
                mode: 'ace/mode/java',
                fontSize: 13,
                readOnly: true,
                useWorker: false,
                showPrintMargin: false,
                highlightActiveLine: false,
                autoScrollEditorIntoView: true
            };

        function getPackageName(name) {
            var dotIndex = name.lastIndexOf('.');
            return (dotIndex == -1 ? '' : name.substring(0, dotIndex));
        }

        function getClassName(name) {
            var dotIndex = name.lastIndexOf('.');
            return (dotIndex == -1 ? name : name.substring(dotIndex + 1));
        }

        self.init = function() {
            self.templates.dao = jQuery('#dao-template').text().trim();
            self.templates.model = jQuery('#model-template').text().trim();
            self.templates.mapper = jQuery('#mapper-template').text().trim();

            $scope.$watch('vm.daoName', function(newValue) {
                parameters.dao['name'] = newValue;
                parameters.dao['packageName'] = getPackageName(newValue);
                parameters.dao['className'] = getClassName(newValue);
            });

            $scope.$watch('vm.modelName', function(newValue) {
                parameters.model['name'] = newValue;
                parameters.model['packageName'] = getPackageName(newValue);
                parameters.model['className'] = getClassName(newValue);
            });

            $scope.$watch('vm.mapperName', function(newValue) {
                parameters.mapper['name'] = newValue;
            });

            self.daoName = 'app.auth.dao.UserDAO';
            self.modelName = 'app.auth.model.UserModel';
            self.mapperName = 'UserMapper';

            $timeout(function() {
                // sql editor
                sqlEditor = ace.edit('sql-editor');
                sqlEditor.setOptions({
                    mode: 'ace/mode/sql',
                    fontSize: 13,
                    showPrintMargin: false,
                    autoScrollEditorIntoView: true,
                    enableBasicAutocompletion: true,
                    enableSnippets: true,
                    enableLiveAutocompletion: true
                });
                sqlEditor.on('change', function(e) {
                    var hasSql = !/^\s*$/.test(sqlEditor.getValue());
                    if (self.hasSql != hasSql) {
                        self.hasSql = hasSql;
                        $timeout(function() { $scope.$apply(); });
                    }
                });
                $timeout(function() {
                    sqlEditor.setValue([
                            'CREATE TABLE `users` (',
                            '    `username` VARCHAR(30) NOT NULL,',
                            '    `password` VARCHAR(60) NOT NULL,',
                            '    PRIMARY KEY (`username`)',
                            ');'
                        ].join('\n'), -1);
                    sqlEditor.resize();
                });

                // dao viewer
                daoViewer = ace.edit('dao-viewer');
                daoViewer.setOptions(javaOptions);
                // model viewer
                modelViewer = ace.edit('model-viewer');
                modelViewer.setOptions(javaOptions);
                // mapper viewer
                mapperViewer = ace.edit('mapper-viewer');
                mapperViewer.setOptions({
                    mode: 'ace/mode/xml',
                    fontSize: 13,
                    readOnly: true,
                    useWorker: false,
                    showPrintMargin: false,
                    highlightActiveLine: false,
                    autoScrollEditorIntoView: true
                });

                new Clipboard('#copy', {
                    text: function() {
                        if (self.activeTab == 0) {
                            return daoViewer.getValue();
                        } else if (self.activeTab == 1) {
                            return modelViewer.getValue();
                        } else if (self.activeTab == 2) {
                            return mapperViewer.getValue();
                        }
                    }
                }).on('success', function(e) {
                    toastr.success('已复制到剪贴板！');
                });
            });
        }

        self.callback = function(height, width) {
            // size of input editor
            jQuery('.x-mapper:eq(0) .tab-pane').css('height', (height - 229) + 'px');
            jQuery('.x-mapper:eq(0) .tab-pane pre').css('min-height', (height - 229) + 'px');
            // size of output editor
            jQuery('.x-mapper:eq(1) .tab-pane').css('height', (height - 57) + 'px');
            jQuery('.x-mapper:eq(1) .tab-pane pre').css('height', (height - 57) + 'px');
        }

        self.save = function() {
            if (self.activeTab == 0) {
                xUtil.file.save(parameters.dao['className'] + '.java', daoViewer.getValue());
            } else if (self.activeTab == 1) {
                xUtil.file.save(parameters.model['className'] + '.java', modelViewer.getValue());
            } else if (self.activeTab == 2) {
                xUtil.file.save(parameters.mapper['name'] + '.xml', mapperViewer.getValue());
            }
        }

        self.reset = function() {
            self.activeTab = 0;
            self.daoName = '';
            self.modelName = '';
            self.mapperName = '';
            self.error = '';
            self.hasSql = false;
            self.hasCode = false;
            // editor & viewer
            sqlEditor.setValue('', -1);
            daoViewer.setValue('', -1);
            modelViewer.setValue('', -1);
            mapperViewer.setValue('', -1);
        }

        self.mapping = function(valid) {
            daoViewer.setValue('', -1);
            modelViewer.setValue('', -1);
            mapperViewer.setValue('', -1);
            self.error = '';
            self.hasCode = false;

            if (valid) {
                var rfields = /create table ([\w`]+)[^\(]*\(([\s\S]+)\)/mgi,
                    rfield = /\s*([0-9A-Za-z_`]+)\s+([0-9A-Za-z_]+(\(\d+(,\s*\d+)?\))?).*,?/g,
                    rtypes = /([0-9A-Za-z_]+)(\((\d+)(,\s*(\d+))?\))?/,
                    match = null;

                parameters.table.name = '';
                parameters.table.columns = [];

                match = rfields.exec(sqlEditor.getValue());
                if (match && match.length > 2) {
                    // table name
                    parameters.table.name = match[1].replace(/[^\w]/g, '');
                    // table fields
                    var fieldsString = match[2];
                    var primaryKeys = [];
                    while ((match = rfield.exec(fieldsString))) {
                        var name = match[1].replace(/[^\w]/g, '');
                        if (!/^\s+(primary|unique|constraint|foreign)\s+key/i.test(match[0])) {
                            var field = {
                                name: name,
                                jdbcType: '',
                                javaName: getFieldName(name),
                                javaType: ''
                            };
                            // match field type
                            matchCustomizedType(field, match[0]);
                            if (!field.jdbcType || !field.javaType) {
                                // jdbc type
                                if (!field.jdbcType) {
                                    var typeMatch = rtypes.exec(match[2]);
                                    if (typeMatch) {
                                        var jdbcType = typeMatch[1].toUpperCase();
                                        switch(jdbcType) {
                                            case 'TEXT':
                                                field.jdbcType = 'VARCHAR';
                                                break;
                                            case 'INT':
                                                field.jdbcType = 'INTEGER';
                                                break;
                                            default:
                                                field.jdbcType = jdbcType;
                                                break;
                                        }
                                    }
                                }
                                // java type
                                if (!field.javaType) {
                                    // java type mapping
                                    switch(field.jdbcType) {
                                        case 'CHAR':
                                        case 'VARCHAR':
                                        case 'TEXT':
                                            field.javaType = 'String';
                                            break;
                                        case 'NUMBER':
                                        case 'TINYINT':
                                        case 'SMALLINT':
                                        case 'MEDIUMINT':
                                        case 'INT':
                                        case 'INTEGER':
                                            field.javaType = 'Integer';
                                            break;
                                        case 'BIGINT':
                                            field.javaType = 'Long';
                                            break;
                                        case 'FLOAT':
                                            field.javaType = 'Float';
                                            break;
                                        case 'DOUBLE':
                                        case 'DECIMAL':
                                        case 'NUMERIC':
                                            field.javaType = 'Double';
                                            break;
                                        case 'DATE':
                                        case 'TIME':
                                        case 'DATETIME':
                                        case 'TIMESTAMP':
                                            field.javaType = 'Date';
                                            break;
                                        default:
                                            field.javaType = 'Object';
                                            break;
                                    }
                                }
                            }
                            parameters.table.columns.push(field);
                        }
                        // primary key
                        var keyMatch = null;
                        if ((keyMatch = match[0].match(/^\s+primary\s+key\s+\((.+)\)/i))) {
                            var rid = /([\w`]+),?/g,
                                idMatch = null;
                            while ((idMatch = rid.exec(keyMatch[1]))) {
                                primaryKeys.push(idMatch[1].replace(/[^\w]/g, ''));
                            }
                        } else if (/primary\s+key/i.test(match[0])) {
                            primaryKeys.push(name);
                        }
                    }
                    _.forEach(primaryKeys, function(primaryKey) {
                        _.forEach(parameters.table.columns, function(column) {
                            if (column.name == primaryKey) {
                                column.primaryKey = true;
                            }
                        });
                    });
                }

                try {
                    daoViewer.setValue(applyTemplate('dao'), -1);
                    daoViewer.resize();
                    modelViewer.setValue(applyTemplate('model'), -1);
                    modelViewer.resize();
                    mapperViewer.setValue(applyTemplate('mapper'), -1);
                    mapperViewer.resize();
                    // enable copy & save
                    self.hasCode = true;
                } catch (e) {
                    self.error = e;
                }
            }
        }

        function getFieldName(name) {
            var names = name.split(/\-+|_+/);
            var fieldName = '';
            if (names.length == 1) {
                fieldName = name.substring(0, 1).toLowerCase() + name.substring(1);
            } else {
                fieldName = names[0].toLowerCase();
                for (var i = 1; i < names.length; i++) {
                    fieldName += names[i].substring(0, 1).toUpperCase() + names[i].substring(1);
                }
            }
            return fieldName;
        }

        function matchCustomizedType(field, input) {
            if (input.match(/\{((jdbcType|javaType):([0-9A-Za-z]+),?)+\}/g)) {
                var rtype = /([A-Za-z]+):([0-9A-Za-z]+)/g,
                    match = null;
                while ((match = rtype.exec(input))) {
                    field[match[1]] = match[2];
                }
            }
        }

        function applyTemplate(templateName) {
            return _.template(self.templates[templateName])(parameters).trim();
        }
    });
});
